package com.ideal.frame.node.service;

import cn.hutool.core.util.ObjectUtil;
import com.ideal.frame.jpa.DSLPageRequest;
import com.ideal.frame.jpa.JpaRepositoryImpl;
import com.ideal.frame.node.dto.NodeSearchForm;
import com.ideal.frame.node.entity.HeartbeatNodeTb;
import com.ideal.frame.node.entity.NodeTb;
import com.ideal.frame.node.entity.QNodeTb;
import com.ideal.frame.node.enumeration.NodeLevel;
import com.ideal.frame.node.enumeration.NodeType;
import com.ideal.frame.node.form.search.NodeSyncParams;
import com.ideal.frame.node.netty.CurrentNode;
import com.ideal.frame.nodecertificate.entity.NodeCertificate;
import com.ideal.frame.nodecertificate.entity.QNodeCertificate;
import com.ideal.frame.prefix.entity.PrefixNetxTb;
import com.ideal.frame.prefix.service.PrefixNextTbService;
import com.ideal.frame.sys.dto.UserDto;
import com.ideal.frame.util.UserUtils;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.jpa.impl.JPAQuery;
import io.jsonwebtoken.lang.Assert;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.Page;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import javax.annotation.PostConstruct;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import java.util.Date;
import java.util.List;
import java.util.Optional;

/**
 * @Auther: liudingsheng
 * @Date: 2021/8/27
 * @Description:
 */
@Service
@Slf4j
public class NodeService {

    @PersistenceContext
    protected EntityManager entityManager;
    private JpaRepositoryImpl<NodeTb, Long> nodeTbLongJpaRepository;
    private JpaRepositoryImpl<HeartbeatNodeTb, Long> heartbeatNodeTbLongJpaRepository;
    private JpaRepositoryImpl<NodeCertificate, Long> nodeCertificateLongJpaRepository;

    QNodeTb qNodeTb = QNodeTb.nodeTb;
    QNodeCertificate qNodeCertificate = QNodeCertificate.nodeCertificate;

    @Value("${node.level}")
    private Integer nodeLevel;
    @Value("${node.prefix}")
    private String nodePrefix;
    @Autowired
    private NodeSyncService nodeSyncService;
    @Autowired
    private NodeSyncEventService nodeSyncEventService;
    @Autowired
    private PrefixNextTbService prefixNextTbService;
    @Autowired
    private CurrentNode currentNode;
    @Autowired
    private RedisTemplate redisTemplate;

    @PostConstruct
    public void initRepo() {
        nodeCertificateLongJpaRepository = new JpaRepositoryImpl<>(NodeCertificate.class, entityManager);
        nodeTbLongJpaRepository = new JpaRepositoryImpl<>(NodeTb.class, entityManager);
        heartbeatNodeTbLongJpaRepository = new JpaRepositoryImpl<>(HeartbeatNodeTb.class, entityManager);
    }

    /**
     * 查询需要同步的节点列表
     *
     * @param params
     * @return
     */
    public List<NodeTb> findSyncNodeData(NodeSyncParams params) {
        JPAQuery<NodeTb> query = nodeTbLongJpaRepository.selectFrom(qNodeTb)
                .where(qNodeTb.syncSourcePrefix.eq(params.getPrefix()))
                .orderBy(qNodeTb.updatedate.asc());
        if (!StringUtils.isEmpty(params.getSyncTime())) {
            query.where(qNodeTb.updatedate.gt(params.getSyncTime()));
        }
        return query.fetch();
    }

    /**
     * 查询需要同步的节点列表
     *
     * @param params
     * @return
     */
    public List<NodeTb> findOtherSyncNodeData(NodeSyncParams params) {
        JPAQuery<NodeTb> query = nodeTbLongJpaRepository.selectFrom(qNodeTb)
                .where(qNodeTb.syncSourcePrefix.ne(params.getPrefix()))
                .orderBy(qNodeTb.updatedate.asc());
        if (!StringUtils.isEmpty(params.getSyncTime())) {
            query.where(qNodeTb.updatedate.gt(params.getSyncTime()));
        }
        return query.fetch();
    }

    /**
     * 节点数据同步
     *
     * @param list
     */
    @Transactional(rollbackFor = Exception.class)
    public void syncNodeData(List<NodeTb> list) {
        if (list != null && list.size() > 0) {
            list.forEach(t -> {
                long id = t.getId();
                // 判断之前该节点是否已经同步过
                NodeTb node = findByNodeForSyncId(id, t.getSyncSourcePrefix());
                t.setSyncId(id);
                if (node != null) {
                    t.setId(node.getId());
                } else {
                    t.setId(null);
                }
                // 获取节点前缀信息
                PrefixNetxTb prefixNetxTb = prefixNextTbService.findByPrefixCode(t.getPrefixNext(), t.getSyncSourcePrefix());
                if (prefixNetxTb != null) {
                    t.setPrefix(prefixNetxTb);
                }
                t.setIssync(Boolean.TRUE);
                saveAndFlush(t);
            });
            nodeSyncEventService.saveSyncEvent(2, list.get(list.size() - 1).getUpdatedate(), nodePrefix);
        }
    }

    private NodeTb findByNodeForSyncId(Long syncId, String sourcePrefix) {
        return nodeTbLongJpaRepository.selectFrom(qNodeTb)
                .where(qNodeTb.isdelete.isFalse())
                .where(qNodeTb.syncSourcePrefix.eq(sourcePrefix))
                .where(qNodeTb.syncId.eq(syncId)).fetchFirst();
    }

    public Page<NodeTb> getPage(NodeSearchForm nodeSearchForm) {
        DSLPageRequest pageRequest = nodeSearchForm.asDslPageRequest();
        if (nodeSearchForm.getNodeId() != null) {
            pageRequest.addWhere(qNodeTb.id.eq(nodeSearchForm.getNodeId()));
        }
        if (nodeSearchForm.getNodeName() != null) {
            pageRequest.addWhere(qNodeTb.nodename.like("%" + nodeSearchForm.getNodeName() + "%"));
        }
        if (nodeSearchForm.getIp() != null) {
            pageRequest.addWhere(qNodeTb.nodeip.like("%" + nodeSearchForm.getIp() + "%"));
        }
        if (nodeSearchForm.getPort() != null) {
            pageRequest.addWhere(qNodeTb.nodeport.like("%" + nodeSearchForm.getPort() + "%"));
        }
        pageRequest.addWhere(qNodeTb.isdelete.isFalse());
        return nodeTbLongJpaRepository.findAll(pageRequest);
    }

    public NodeTb getById(Long id) {
        Optional<NodeTb> nodeTb = nodeTbLongJpaRepository.findById(id);
        return nodeTb.isPresent() ? nodeTb.get() : null;
    }

    /**
     * 获取配置文件中的本级节点信息（本级节点配在yml中，没有配在db）
     *
     * @return
     */
    public NodeTb getByLevel() {
        // 获取当前节点
        NodeTb selfNode = new NodeTb();
        selfNode.setId(currentNode.getId());
        selfNode.setNodename(currentNode.getName());
        selfNode.setNodelevel(currentNode.getLevel());
        selfNode.setNodetype(currentNode.getType());
        selfNode.setNettymonitor(true);
        selfNode.setNettyport(currentNode.getNettyport());
        PrefixNetxTb prefixNetxTb = new PrefixNetxTb();
        prefixNetxTb.setPrefixNext(currentNode.getPrefix());
        selfNode.setPrefix(prefixNetxTb);
        selfNode.setNodeip(currentNode.getIp());
        selfNode.setNodeport(currentNode.getPort());
        return selfNode;
    }

    /**
     * 获取所有支持心跳检测的节点
     *
     * @return
     */
    public List<NodeTb> getAllNodes() {
        // 获取当前节点
        NodeTb nodeTb = new NodeTb();
        nodeTb.setIsdelete(false);
        List<NodeTb> nodeList = this.nodeTbLongJpaRepository.findAll(Example.of(nodeTb));
        return nodeList;
    }

    /**
     * 获取所有没有生成证书的节点
     *
     * @return
     */
    public List<NodeTb> getAllNotHasCerNodes() {
        // 获取当前节点
        NodeTb nodeTb = new NodeTb();
        nodeTb.setIsdelete(false);
        return this.nodeTbLongJpaRepository.selectFrom(qNodeTb)
                .where(nodeCertificateLongJpaRepository.selectFrom(qNodeCertificate).where(qNodeCertificate.nodeType.eq(1)).where(qNodeCertificate.nodeId.eq(qNodeTb.id)).notExists()).fetch();
    }

    /**
     * 获取所有取消心跳检测的节点
     *
     * @return
     */
    public List<NodeTb> getAllCancelHeartbeatNodes() {
        NodeTb nodeTb = new NodeTb();
        nodeTb.setIsdelete(false);
        nodeTb.setNettymonitor(false);
        List<NodeTb> nodeList = this.nodeTbLongJpaRepository.findAll(Example.of(nodeTb));
        return nodeList;
    }

    /**
     * 获取配置文件中node.level对应的节点，并判断是否可以成为netty-server
     * 上级节点作为netty-client, 下级节点作为netty-server， client同时连接多个server
     *
     * @return
     */
    public boolean canBeNettyServer() {
        NodeTb selfNode = this.getByLevel();
        // 虚拟节点
        if (NodeType.VIRTUAL_NODE.getValue() == selfNode.getNodetype()) {
            log.info("虚拟节点, 不创建netty-server");
            return false;
        }
        // 叶子节点
        if (NodeLevel.ROOT.getValue() == selfNode.getNodelevel()) {
            log.info("根节点, 不创建netty-server");
            return false;
        }
        log.info("开始创建netty-server");
        return true;
    }

    /**
     * 获取配置文件中node.level对应的节点，并判断是否可以成为netty-client
     * 上级节点作为netty-client, 下级节点作为netty-server， client同时连接多个server
     *
     * @return
     */
    public boolean canBeNettyClient() {
        NodeTb selfNode = this.getByLevel();
        // 虚拟节点
        if (NodeType.VIRTUAL_NODE.getValue() == selfNode.getNodetype()) {
            log.info("虚拟节点, 不创建netty-client");
            return false;
        }
        // 根节点
        if (NodeLevel.THIRD.getValue() == selfNode.getNodelevel()) {
            log.info("叶子节点, 不创建netty-client");
            return false;
        }
        log.info("开始创建netty-client");
        return true;
    }

    @Transactional(rollbackFor = Exception.class)
    public void save(NodeTb nodeTb) {
        if (nodeTb.getId() != null) {
            // 更新
            nodeTb.setUpdatedate(new Date());
        } else {
            // 新增
            nodeTb.setInsertdate(new Date());
            nodeTb.setUpdatedate(new Date());
            nodeTb.setIsdelete(false);
            nodeTb.setIssync(Boolean.FALSE);
        }
        nodeTbLongJpaRepository.save(nodeTb);
    }

    @Transactional(rollbackFor = Exception.class)
    public void saveNode(NodeTb nodeTb) {

        Date date = new Date();
        if (nodeTb.getId() == null) {
            nodeTb.setInsertdate(date);
            nodeTb.setIssync(Boolean.FALSE);
            nodeTb.setSyncSourcePrefix(nodePrefix);
        } else {
            if (nodeLevel == -1 || nodeLevel == 0) {
                NodeTb node = nodeTbLongJpaRepository.getOne(nodeTb.getId());
                if (node != null && node.getIssync() != null) {
                    Assert.isTrue(!node.getIssync(), "当前节点由其他根节点同步，不允许修改");
                }
            }
        }
        nodeTb.setUpdatedate(date);
        nodeTb.setIsdelete(false);
        nodeTb.setStatus(false);
        nodeTb.setNettymonitor(false);
        nodeTbLongJpaRepository.save(nodeTb);
        // 如果是一级节点，则需要将节点给同步到其他根节点
        if (nodeLevel == -1 || nodeLevel == 0) {
            nodeSyncService.syncNode();
        }
    }

    @Transactional(rollbackFor = Exception.class)
    public void saveAndFlush(NodeTb nodeTb) {
        nodeTbLongJpaRepository.saveAndFlush(nodeTb);
    }

    public boolean checkNodeHasPrefix(Long prefixId, Long nodeId) {
        BooleanExpression booleanExpression = qNodeTb.prefix.id.eq(prefixId).and(qNodeTb.isdelete.isFalse()).and(qNodeTb.issync.isFalse());
        if (nodeId != null) {
            booleanExpression = booleanExpression.and(qNodeTb.id.ne(nodeId)).and(qNodeTb.issync.isFalse());
        }
        return nodeTbLongJpaRepository.exists(booleanExpression);
    }

    @Transactional(rollbackFor = Exception.class)
    public void deleteNode(Long nodeId) {
        if (nodeLevel == -1 || nodeLevel == 0) {
            NodeTb node = nodeTbLongJpaRepository.getOne(nodeId);
            if (node != null) {
                Assert.isTrue(!node.getIssync(), "当前节点由其他根节点同步，不允许删除");
            }
        }
        nodeTbLongJpaRepository.updateClause()
                .set(qNodeTb.isdelete, true)
                .set(qNodeTb.updatedate, new Date())
                .where(qNodeTb.id.eq(nodeId)).execute();
        // 如果是一级节点，则需要将节点给同步到其他根节点
        if (nodeLevel == -1 || nodeLevel == 0) {
            NodeTb node = nodeTbLongJpaRepository.getOne(nodeId);
            nodeSyncService.syncNode();
        }
    }

    @Transactional
    public void clientNotify(Long nodeId) {
        Optional<NodeTb> optional = nodeTbLongJpaRepository.findOne(qNodeTb.id.eq(nodeId));
        Assert.isTrue(optional.isPresent(), "当前节点不存在");
        NodeTb childNodeTb = optional.get();
        //TODO 心跳检测

    }


    public long getNextNodeCount() {
        return nodeTbLongJpaRepository.count(qNodeTb.isdelete.isFalse());
    }

    @Transactional
    public void joinInMonitor(Long nodeId) {
        nodeTbLongJpaRepository.updateClause().set(qNodeTb.nettymonitor, true).where(qNodeTb.id.eq(nodeId)).execute();
    }

    @Transactional
    public void excludeMonitor(Long nodeId) {
        nodeTbLongJpaRepository.updateClause().set(qNodeTb.nettymonitor, false).where(qNodeTb.id.eq(nodeId)).execute();
    }

    public boolean existsNode(Long nodeId) {
        return nodeTbLongJpaRepository.existsById(nodeId);
    }

    /**
     * 前端选择的虚节点后缓存到redis
     *
     * @param nodeId 节点id
     */
    @Transactional(rollbackFor = Exception.class)
    public void chooseVirtualNode(Long nodeId) {
        UserDto userDto = UserUtils.getCurrentUser();
        String key = "virtual_node：" + userDto.getId();
        // 选择的节点为yml中配置的节点 直接清空redis
        if (ObjectUtil.equal(currentNode.getId(), nodeId)) {
            redisTemplate.delete(key);
        } else {
            Optional<NodeTb> optional = nodeTbLongJpaRepository.findOne(qNodeTb.id.eq(nodeId));
            Assert.isTrue(optional.isPresent(), "节点不存在");
            redisTemplate.opsForValue().set(key, nodeId);
        }
    }

    /**
     * 查询当前选择的虚节点id
     *
     * @return null 表示选择的是yml中配置的实节点
     */
    @Transactional(rollbackFor = Exception.class)
    public NodeTb getSelectedVirtualNodeId() {
        UserDto userDto = UserUtils.getCurrentUser();
        String key = "virtual_node：" + userDto.getId();
        Object o = redisTemplate.opsForValue().get(key);
        if (o != null) {
            Optional<NodeTb> optional = nodeTbLongJpaRepository.findById((Long) o);
            if (optional.isPresent() && ObjectUtil.equal(2, optional.get().getNodetype())) {
                return optional.get();
            } else {
                redisTemplate.delete(key);
            }
        }
        return null;
    }
}
